跳到主要内容

C++ 38-40 操作符分析

38 逻辑操作符的陷阱

逻辑运算符的原生语义

  • 操作数只有两种值(true和false)
  • 逻辑表达式不用完全计算就能确定最终值
  • 最终结果只能是true或者false

编程实验:逻辑表达式

#include <iostream>
#include <string>

using namespace std;

int func(int i) {
cout << "int func(int i) : i = " << i << endl;
return i;
}

int main() {
if( func(0) && func(1) ) {
cout << "Result is true!" << endl;
} else {
cout << "Result is false!" << endl;
}
cout << endl;
if( func(0) || func(1) ) {
cout << "Result is true!" << endl;
} else {
cout << "Result is false!" << endl;
}
return 0;
}
/* 输出
int func(int i) : i = 0
Result is false!

int func(int i) : i = 0
int func(int i) : i = 1
Result is true!
*/

逻辑操作符可以重载吗?重载逻辑操作符有什么意义?

编程实验:重载逻辑操作符

#include <iostream>
#include <string>

using namespace std;

class Test {
int mValue;
public:
Test(int v) {
mValue = v;
}
int value() const {
return mValue;
}
};

bool operator && (const Test& l, const Test& r) {
return l.value() && r.value();
}

bool operator || (const Test& l, const Test& r) {
return l.value() || r.value();
}

Test func(Test i) {
cout << "Test func(Test i) : i.value() = " << i.value() << endl;
return i;
}

int main() {
Test t0(0);
Test t1(1);
if( func(t0) && func(t1) ) {
cout << "Result is true!" << endl;
} else {
cout << "Result is false!" << endl;
}
cout << endl;
if( func(1) || func(0) ) {
cout << "Result is true!" << endl;
} else {
cout << "Result is false!" << endl;
}
return 0;
}
/*输出
Test func(Test i) : i.value() = 1
Test func(Test i) : i.value() = 0
Result is false!

Test func(Test i) : i.value() = 0
Test func(Test i) : i.value() = 1
Result is true!
*/

问题的本质分析

  1. C++通过函数调用扩展操作符的功能
  2. 进入函数体前必须完成所有参数的计算
  3. 函数参数的计算次序是不定的
  4. 短路法则完全失效

逻辑操作符重载后无法完全实现原生的语义

一些有用的建议

  • 实际工程开发中避免重载逻辑操作符
  • 通过重载比较符代替逻辑操作符重载
  • 直接使用成员函数代替逻辑操作符重载
  • 使用全局函数对逻辑操作符进行重载

小结

  • C++从语法上支持逻辑操作符
  • 重载后的逻辑操作符不满足短路法则
  • 工程开发中不要重载逻辑操作符
  • 通过重载比较操作符替换逻辑操作符重载
  • 通过专用成员函数替换逻辑操作符重载

39 逗号操作符的分析

逗号操作符,可以构成逗号表达式

  • 逗号表达式用于将多个子表达式连接为一个表达式
  • 逗号表达式的值为最后一个子表达式的值
  • 逗号表达式中的前N-1个子表达式可以没有返回值
  • 逗号表达式按照从左往右的顺序计算每个子表达式的值:exp1,exp2,exp3, ... ,expN

实例分析:逗号表达式的示例

#include <iostream>
#include <string>

using namespace std;

void func(int i) {
cout << "func() : i = " << i << endl;
}

int main() {
int a[3][3] = {(0, 1, 2), (3, 4, 5), (6, 7, 8)};
int i = 0;
int j = 0;
while (i < 5) func(i), i++;

for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
cout << a[i][j] << " ";
}
}
cout << endl;
(i, j) = 6;
cout << "i = " << i << endl;
cout << "j = " << j << endl;
return 0;
}
/*输出
func() : i = 0
func() : i = 1
func() : i = 2
func() : i = 3
func() : i = 4
2 5 8 0 0 0 0 0 0
i = 3
j = 6
*/

重载逗号操作符

  • 在C++中重载逗号操作符是合法的
  • 使用全局函数对逗号操作符进行重载
  • 重载函数的参数必须有一个是类类型
  • 重载函数的返回值类型必须是引用
Class& operator , (const Class &a,const Class &b) {
return const_cast<Class&>(b);
}

编程实验:重载逗号操作符

#include <iostream>
#include <string>

using namespace std;

class Test {
int mValue;

public:
Test(int i) {
mValue = i;
}
int value() {
return mValue;
}
};

Test &operator,(const Test &a, const Test &b) {
return const_cast<Test &>(b);
}

Test func(Test &i) {
cout << "func() : i = " << i.value() << endl;

return i;
}

int main() {
Test t0(0);
Test t1(1);
Test tt = (func(t0), func(t1)); // Test tt = func(t1);

cout << tt.value() << endl; // 1

return 0;
}
/*输出
func() : i = 0
func() : i = 1
1
*/

问题的本质分析

  1. C++通过函数调用扩展操作符的功能
  2. 进入函数体前必须完成所有参数的计算
  3. 函数参数的计算次序是不定的
  4. 重载后无法严格从左向右计算表达式

工程中不要重载逗号操作符!

小结

  • 逗号表达式从左向右顺序计算每个子表达式的值
  • 逗号表达式的值为最后一个子表达式的值
  • 操作符重载无法完全实现逗号操作符的原生意义
  • 工程开发中不要重载逗号操作符

40 前置操作符和后置操作符

下面的代码有没有区别?为什么?

i++;    //i的值作为返回值,i自增1
++i; //i自增1,i的值作为返回值

编程实验:真的有区别吗?

#include <iostream>
#include <string>

using namespace std;

int main() {
int i = 0;
i++;
++i;
return 0;
}
  • 现代编译产品会对代码进行优化
  • 优化使得最终的二进制程序更加高效
  • 优化后的二进制程序丢失了C/C++的原生语义
  • 不可能从编译后的二进制程序还原C/C++程序

C/C++开发的软件无法完全反编译!

思考:++操作符可以重载吗?如何区分前置++和后置++

++操作符重载

  • 全局函数和成员函数均可进行重载
  • 重载前置++操作符不需要额外的参数
  • 重载后置++操作符需要一个int类型的占位参数

编程实验:++操作符的重载

#include <iostream>
#include <string>

using namespace std;

class Test {
int mValue;
public:
Test(int i) {
mValue = i;
}

int value() {
return mValue;
}

Test& operator ++ () {
++mValue;
return *this;
}

Test operator ++ (int) {
Test ret(mValue);
mValue++;
return ret;
}
};

int main() {
Test t(0);
t++;
++t;
return 0;
}

对于基础类型的变量

  • 前置++的效率与后置++的效率基本相同
  • 根据项目组编码规范进行选择

对于类类型的对象

  • 前置++的效率高于后置++
  • 尽量使用前置++操作符提高程序效率

编程实验:复数类的进一步完善

//Complex.h
#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex {
double a;
double b;
public:
Complex(double a = 0, double b = 0);
double getA();
double getB();
double getModulus();

Complex operator + (const Complex& c);
Complex operator - (const Complex& c);
Complex operator * (const Complex& c);
Complex operator / (const Complex& c);

bool operator == (const Complex& c);
bool operator != (const Complex& c);

Complex& operator = (const Complex& c);

Complex& operator ++ ();
Complex operator ++ (int);
};
#endif
#include "Complex.h"
#include "math.h"

Complex::Complex(double a, double b) {
this->a = a;
this->b = b;
}

double Complex::getA() {
return a;
}

double Complex::getB() {
return b;
}

double Complex::getModulus() {
return sqrt(a * a + b * b);
}

Complex Complex::operator + (const Complex& c) {
double na = a + c.a;
double nb = b + c.b;
Complex ret(na, nb);

return ret;
}

Complex Complex::operator - (const Complex& c) {
double na = a - c.a;
double nb = b - c.b;
Complex ret(na, nb);

return ret;
}

Complex Complex::operator * (const Complex& c) {
double na = a * c.a - b * c.b;
double nb = a * c.b + b * c.a;
Complex ret(na, nb);

return ret;
}

Complex Complex::operator / (const Complex& c) {
double cm = c.a * c.a + c.b * c.b;
double na = (a * c.a + b * c.b) / cm;
double nb = (b * c.a - a * c.b) / cm;
Complex ret(na, nb);

return ret;
}

bool Complex::operator == (const Complex& c) {
return (a == c.a) && (b == c.b);
}

bool Complex::operator != (const Complex& c) {
return !(*this == c);
}

Complex& Complex::operator = (const Complex& c) {
if( this != &c ) {
a = c.a;
b = c.b;
}
return *this;
}

Complex& Complex::operator ++ () {
a = a + 1;
b = b + 1;

return *this;
}

Complex Complex::operator ++ (int) {
Complex ret(a, b);

a = a + 1;
b = b + 1;

return ret;
}

小结

  • 编译优化使得最终的可执行程序更加高效
  • 前置++操作符和后置++操作符都可以被重载
  • ++操作符的重载必须符合其原生语义
  • 对于基础类型,前置++与后置++的效率继续相同
  • 对于类类型,前置++的效率高于后置++